
#include "../HardwareConfig.h"

#include "../Headers/Microchip/usb_config.h"
#include "../Headers/Microchip/usb.h"
#include  "../Headers/Microchip/usb_function_cdc.h"
#include "Regulator.h"

char USB_Out_Buffer[CDC_DATA_OUT_EP_SIZE];

void initializeUSB(void) {

    unsigned char i;

#if defined(USB_INTERRUPT)
    USBDeviceAttach();
#endif
#if defined(USE_USB_BUS_SENSE_IO)
    tris_usb_bus_sense = INPUT_PIN; // See HardwareProfile.h
#endif

#if defined(USE_SELF_POWER_SENSE_IO)
    tris_self_power = INPUT_PIN; // See HardwareProfile.h
#endif


    for (i = 0; i<sizeof (USB_Out_Buffer); i++) {
        USB_Out_Buffer[i] = 0;
    }

    USBDeviceInit();
}

void receivedUSBPacketHandler(UINT8 *_USB_In_Buffer) {

    UINT16 desiredVoltage, desiredMaximumCurrent;
    UINT16 outputVoltage, outputCurrent;

    switch (_USB_In_Buffer[1]) {


        case HI_COMMAND:
            USB_Out_Buffer[0] = FRAME_START;
            USB_Out_Buffer[1] = HI_COMMAND;
            putUSBUSART(&USB_Out_Buffer[0], 2);
            break;

        case SET_OUTPUTS_COMMAND:
            
            desiredVoltage = ((unsigned int) _USB_In_Buffer[2] << 8) | _USB_In_Buffer[3];
            desiredMaximumCurrent = ((unsigned int) _USB_In_Buffer[4] << 8) | _USB_In_Buffer[5];            
            setDesiredOutputVoltageAndCurrent(desiredVoltage, desiredMaximumCurrent);
            break;

        case READ_OUTPUTS_COMMAND:

            outputVoltage = readSetOutputVoltage();
            USB_Out_Buffer[0] = FRAME_START;
            USB_Out_Buffer[1] = READ_OUTPUTS_COMMAND;
            USB_Out_Buffer[2] = (outputVoltage >> 8) & 0xFF;
            USB_Out_Buffer[3] = outputVoltage & 0xFF;

            outputCurrent = readSetMaximumOutputCurrent();            
            USB_Out_Buffer[4] = (outputCurrent >> 8) & 0xFF;
            USB_Out_Buffer[5] = outputCurrent & 0xFF ;

            putUSBUSART(&USB_Out_Buffer[0], 6);

            break;

        case CONFIG_COMMAND:

            PORTAbits.RA6 = !PORTAbits.RA6;
            USB_Out_Buffer[0] = FRAME_START;
            USB_Out_Buffer[1] = CONFIG_COMMAND;
            putUSBUSART(&USB_Out_Buffer[0], 2);
            break;
        case UART_SEND_COMMAND:
            break;

        default: break;

    }
    CDCTxService();
}


#if defined(USB_CDC_SET_LINE_CODING_HANDLER)

void mySetLineCodingHandler(void) {
    //If the request is not in a valid range
    if (cdc_notice.GetLineCoding.dwDTERate.Val > 115200) {
        //NOTE: There are two ways that an unsupported baud rate could be
        //handled.  The first is just to ignore the request and don't change
        //the values.  That is what is currently implemented in this function.
        //The second possible method is to stall the STATUS stage of the request.
        //STALLing the STATUS stage will cause an exception to be thrown in the
        //requesting application.  Some programs, like HyperTerminal, handle the
        //exception properly and give a pop-up box indicating that the request
        //settings are not valid.  Any application that does not handle the
        //exception correctly will likely crash when this requiest fails.  For
        //the sake of example the code required to STALL the status stage of the
        //request is provided below.  It has been left out so that this demo
        //does not cause applications without the required exception handling
        //to crash.
        //---------------------------------------
        //USBStallEndpoint(0,1);
    } else {
        //Update the baudrate info in the CDC driver
        CDCSetBaudRate(cdc_notice.GetLineCoding.dwDTERate.Val);

        //Update the baudrate of the UART
#if defined(__18CXX)
        {
            DWORD_VAL dwBaud;
            dwBaud.Val = (DWORD) (GetSystemClock() / 4) / line_coding.dwDTERate.Val - 1;
            SPBRG = dwBaud.v[0];
            SPBRGH = dwBaud.v[1];
        }
#elif defined(__C30__)
        {
            DWORD_VAL dwBaud;
#if defined(__dsPIC33EP512MU810__) || defined (__PIC24EP512GU810__)
            dwBaud.Val = ((GetPeripheralClock() / (unsigned long) (16 * line_coding.dwDTERate.Val))) - 1;
#else
            dwBaud.Val = (((GetPeripheralClock() / 2)+(BRG_DIV2 / 2 * line_coding.dwDTERate.Val)) / BRG_DIV2 / line_coding.dwDTERate.Val - 1);
#endif
            U2BRG = dwBaud.Val;
        }
#elif defined(__C32__)
        {
            U2BRG = ((GetPeripheralClock()+(BRG_DIV2 / 2 * line_coding.dwDTERate.Val)) / BRG_DIV2 / line_coding.dwDTERate.Val - 1);
            //U2MODE = 0;
            U2MODEbits.BRGH = BRGH2;
            //U2STA = 0;
        }
#endif
    }
}
#endif

void USBCBSuspend(void) {
    //Example power saving code.  Insert appropriate code here for the desired
    //application behavior.  If the microcontroller will be put to sleep, a
    //process similar to that shown below may be used:

    //ConfigureIOPinsForLowPower();
    //SaveStateOfAllInterruptEnableBits();
    //DisableAllInterruptEnableBits();
    //EnableOnlyTheInterruptsWhichWillBeUsedToWakeTheMicro();	//should enable at least USBActivityIF as a wake source
    //Sleep();
    //RestoreStateOfAllPreviouslySavedInterruptEnableBits();	//Preferrably, this should be done in the USBCBWakeFromSuspend() function instead.
    //RestoreIOPinsToNormal();									//Preferrably, this should be done in the USBCBWakeFromSuspend() function instead.

    //IMPORTANT NOTE: Do not clear the USBActivityIF (ACTVIF) bit here.  This bit is
    //cleared inside the usb_device.c file.  Clearing USBActivityIF here will cause
    //things to not work as intended.


#if defined(__C30__)
#if 0
    U1EIR = 0xFFFF;
    U1IR = 0xFFFF;
    U1OTGIR = 0xFFFF;
    IFS5bits.USB1IF = 0;
    IEC5bits.USB1IE = 1;
    U1OTGIEbits.ACTVIE = 1;
    U1OTGIRbits.ACTVIF = 1;
    Sleep();
#endif
#endif
}

void USBCBWakeFromSuspend(void) {
    // If clock switching or other power savings measures were taken when
    // executing the USBCBSuspend() function, now would be a good time to
    // switch back to normal full power run mode conditions.  The host allows
    // a few milliseconds of wakeup time, after which the device must be
    // fully back to normal, and capable of receiving and processing USB
    // packets.  In order to do this, the USB module must receive proper
    // clocking (IE: 48MHz clock must be available to SIE for full speed USB
    // operation).
}

void USBCBErrorHandler(void) {
    // No need to clear UEIR to 0 here.
    // Callback caller is already doing that.

    // Typically, user firmware does not need to do anything special
    // if a USB error occurs.  For example, if the host sends an OUT
    // packet to your device, but the packet gets corrupted (ex:
    // because of a bad connection, or the user unplugs the
    // USB cable during the transmission) this will typically set
    // one or more USB error interrupt flags.  Nothing specific
    // needs to be done however, since the SIE will automatically
    // send a "NAK" packet to the host.  In response to this, the
    // host will normally retry to send the packet again, and no
    // data loss occurs.  The system will typically recover
    // automatically, without the need for application firmware
    // intervention.

    // Nevertheless, this callback function is provided, such as
    // for debugging purposes.
}

void USBCBCheckOtherReq(void) {
    USBCheckCDCRequest();
}//end

void USBCBStdSetDscHandler(void) {
    // Must claim session ownership if supporting this request
}//end

void USBCBInitEP(void) {
    CDCInitEP();
}

void USBCBSendResume(void) {
    static WORD delay_count;

    //First verify that the host has armed us to perform remote wakeup.
    //It does this by sending a SET_FEATURE request to enable remote wakeup,
    //usually just before the host goes to standby mode (note: it will only
    //send this SET_FEATURE request if the configuration descriptor declares
    //the device as remote wakeup capable, AND, if the feature is enabled
    //on the host (ex: on Windows based hosts, in the device manager
    //properties page for the USB device, power management tab, the
    //"Allow this device to bring the computer out of standby." checkbox
    //should be checked).
    if (USBGetRemoteWakeupStatus() == TRUE) {
        //Verify that the USB bus is in fact suspended, before we send
        //remote wakeup signalling.
        if (USBIsBusSuspended() == TRUE) {
            USBMaskInterrupts();

            //Clock switch to settings consistent with normal USB operation.
            USBCBWakeFromSuspend();
            USBSuspendControl = 0;
            USBBusIsSuspended = FALSE; //So we don't execute this code again,
            //until a new suspend condition is detected.

            //Section 7.1.7.7 of the USB 2.0 specifications indicates a USB
            //device must continuously see 5ms+ of idle on the bus, before it sends
            //remote wakeup signalling.  One way to be certain that this parameter
            //gets met, is to add a 2ms+ blocking delay here (2ms plus at
            //least 3ms from bus idle to USBIsBusSuspended() == TRUE, yeilds
            //5ms+ total delay since start of idle).
            delay_count = 3600U;
            do {
                delay_count--;
            } while (delay_count);

            //Now drive the resume K-state signalling onto the USB bus.
            USBResumeControl = 1; // Start RESUME signaling
            delay_count = 1800U; // Set RESUME line for 1-13 ms
            do {
                delay_count--;
            } while (delay_count);
            USBResumeControl = 0; //Finished driving resume signalling

            USBUnmaskInterrupts();
        }
    }
}

#if defined(ENABLE_EP0_DATA_RECEIVED_CALLBACK)

void USBCBEP0DataReceived(void) {
}
#endif

BOOL USER_USB_CALLBACK_EVENT_HANDLER(USB_EVENT event, void *pdata, WORD size) {
    switch ((INT) event) {
        case EVENT_TRANSFER:
            //Add application specific callback task or callback function here if desired.
            break;
        case EVENT_SOF:
            //USBCB_SOF_Handler();
            //TODO: ADD Debug MEssage
            break;
        case EVENT_SUSPEND:
            USBCBSuspend();
            break;
        case EVENT_RESUME:
            USBCBWakeFromSuspend();
            break;
        case EVENT_CONFIGURED:
            USBCBInitEP();
            break;
        case EVENT_SET_DESCRIPTOR:
            USBCBStdSetDscHandler();
            break;
        case EVENT_EP0_REQUEST:
            USBCBCheckOtherReq();
            break;
        case EVENT_BUS_ERROR:
            USBCBErrorHandler();
            break;
        case EVENT_TRANSFER_TERMINATED:
            //Add application specific callback task or callback function here if desired.
            //The EVENT_TRANSFER_TERMINATED event occurs when the host performs a CLEAR
            //FEATURE (endpoint halt) request on an application endpoint which was
            //previously armed (UOWN was = 1).  Here would be a good place to:
            //1.  Determine which endpoint the transaction that just got terminated was
            //      on, by checking the handle value in the *pdata.
            //2.  Re-arm the endpoint if desired (typically would be the case for OUT
            //      endpoints).
            break;
        default:
            break;
    }
    return TRUE;
}